package com.fight.strive.sys.modules.rbac.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fight.strive.sys.enums.AuthTypeEnum;
import com.fight.strive.sys.modules.exception.StriveException;
import com.fight.strive.sys.modules.mybatisplus.utils.MyBatisPlusUtils;
import com.fight.strive.sys.modules.rbac.constants.RbacAuthConstants;
import com.fight.strive.sys.modules.rbac.dao.RbacAuthMapper;
import com.fight.strive.sys.modules.rbac.dto.RbacAuthDto;
import com.fight.strive.sys.modules.rbac.entity.RbacAuthEntity;
import com.fight.strive.sys.modules.rbac.entity.RbacUserEntity;
import com.fight.strive.sys.modules.rbac.service.RbacAuthService;
import com.fight.strive.sys.modules.rbac.service.RbacRoleAuthService;
import com.fight.strive.sys.modules.rbac.service.RbacUserRoleService;
import com.fight.strive.sys.modules.request.dto.PageRequest;
import com.fight.strive.sys.modules.response.dto.PageData;
import com.fight.strive.sys.modules.sysadmin.utils.SysAdminUtils;
import com.fight.strive.sys.utils.BeanUtils;
import com.fight.strive.sys.utils.CollectionUtils;
import com.fight.strive.sys.utils.ObjectUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.HashSet;
import java.util.List;

/**
 * @author ZHOUXIANG
 */
@Service
@Slf4j
public class RbacAuthServiceImpl
        extends ServiceImpl<RbacAuthMapper, RbacAuthEntity>
        implements RbacAuthService {

    @Resource
    private RbacUserRoleService rbacUserRoleService;

    @Resource
    @Lazy
    private RbacRoleAuthService rbacRoleAuthService;

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public void save(@Valid RbacAuthDto dto) {
        RbacAuthEntity entity = this.getByCodeNotId(
                dto.getAuthCode(), dto.getId());
        if (ObjectUtils.notNull(entity)) {
            throw new StriveException("该权限代码已经存在");
        }
        dto.setAuthTypeName(AuthTypeEnum
                .getAuthTypeNameByCode(dto.getAuthTypeCode()));
        entity = new RbacAuthEntity();
        BeanUtils.copyPropertiesIgnoreNull(dto, entity);
        this.saveOrUpdate(entity);
        // 更新缓存
        this.updateRedisData(entity);
    }

    @Override
    public RbacAuthEntity getByCodeNotId(String code, Long id) {
        QueryWrapper<RbacAuthEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("auth_code", code);
        if (ObjectUtils.notNull(id)) {
            queryWrapper.ne("id", id);
        }
        return this.getOne(queryWrapper);
    }

    @Override
    public PageData<RbacAuthEntity> listByPage(
            PageRequest<RbacAuthDto> pageRequest) {
        QueryWrapper<RbacAuthEntity> queryWrapper = new QueryWrapper<>();
        MyBatisPlusUtils.buildQuery(
                queryWrapper, pageRequest, "authCode");
        IPage<RbacAuthEntity> page = pageRequest.buildMybatisPlusPage();
        IPage<RbacAuthEntity> iPage = this.page(page, queryWrapper);
        return new PageData<>(iPage);
    }

    @Override
    public void deleteById(RbacAuthDto dto) {
        // 删除缓存
        this.deleteRedisData(this.getAuthById(dto.getId()));
        // 删除数据库
        this.removeById(dto.getId());
    }

    @Override
    public void deleteBatch(List<Long> ids) {
        // 删除缓存
        for (Long id : ids) {
            RbacAuthEntity entity = this.getAuthById(id);
            this.deleteRedisData(entity);
        }
        // 删除数据库
        this.removeByIds(ids);
    }

    @Override
    public HashSet<String> getUserAuths(Long userId) {
        HashSet<String> auths = new HashSet<>();
        if (ObjectUtils.isNull(userId)) {
            RbacUserEntity userEntity = SysAdminUtils.getCurrentUserEntity();
            if (ObjectUtils.notNull(userEntity)) {
                userId = userEntity.getId();
            }
        }
        if (ObjectUtils.notNull(userId)) {
            // 获取人员所有角色代码，包含所在组织的角色代码
            HashSet<String> userRoleCodes =
                    rbacUserRoleService.getUserRoles(userId);
            if (CollectionUtils.isNotEmpty(userRoleCodes)) {
                userRoleCodes.forEach(role -> {
                    // 如有缓存直接从缓存中取出
                    HashSet<String> roleAuths =
                            rbacRoleAuthService.getRoleAuth(role);
                    if (CollectionUtils.isNotEmpty(roleAuths)) {
                        auths.addAll(roleAuths);
                    }
                });
            }
        }
        return auths;
    }

    @Override
    public Boolean userHasAuth(Long userId, String authCode) {
        boolean has = false;
        if (ObjectUtils.isNull(userId)) {
            RbacUserEntity userEntity = SysAdminUtils.getCurrentUserEntity();
            if (ObjectUtils.notNull(userEntity)) {
                userId = userEntity.getId();
            }
        }
        if (ObjectUtils.notNull(userId)) {
            // 如有缓存直接从缓存中取出
            HashSet<String> auths = this.getUserAuths(userId);
            if (CollectionUtils.isNotEmpty(auths)
                    && auths.contains(authCode)) {
                has = true;
            }
        }
        return has;
    }

    @Override
    public RbacAuthEntity getAuthById(Long authId) {
        RbacAuthEntity entity = this.getRedisData(
                RbacAuthConstants.AUTH_ID_KEY, authId);
        if (ObjectUtils.isNull(entity)) {
            entity = this.getById(authId);
            if (ObjectUtils.notNull(entity)) {
                this.updateRedisData(entity);
            }
        }
        return entity;
    }

    @Override
    public RbacAuthEntity getAuthByCode(String authCode) {
        Long tenantId = SysAdminUtils.getCurrentTenantId();
        RbacAuthEntity entity = this.getRedisData(
                RbacAuthConstants.AUTH_CODE_ID_KEY
                        .concat(String.valueOf(tenantId)), authCode);
        if (ObjectUtils.isNull(entity)) {
            QueryWrapper<RbacAuthEntity> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("auth_code", authCode);
            entity = this.getOne(queryWrapper, false);
            this.updateRedisData(entity);
        }
        return entity;
    }

    @Override
    public void updateRedisData(RbacAuthEntity entity) {
        // auth id -> auth entity
        redisTemplate.opsForHash().put(
                RbacAuthConstants.AUTH_ID_KEY, entity.getId(), entity);
        // auth code -> auth id
        redisTemplate.opsForHash().put(
                RbacAuthConstants.AUTH_CODE_ID_KEY
                        .concat(String.valueOf(entity.getTenantId())),
                entity.getAuthCode(), entity.getId());
    }

    @Override
    public RbacAuthEntity getRedisData(String key, Object hashKey) {
        if (RbacAuthConstants.AUTH_ID_KEY.equals(key)) {
            return (RbacAuthEntity) redisTemplate.opsForHash().get(
                    RbacAuthConstants.AUTH_ID_KEY, hashKey);
        } else {
            Long authId = (Long) redisTemplate.opsForHash().get(key, hashKey);
            if (ObjectUtils.notNull(authId)) {
                return (RbacAuthEntity) redisTemplate.opsForHash().get(
                        RbacAuthConstants.AUTH_ID_KEY, authId);
            }
        }
        return null;
    }

    @Override
    public void deleteRedisData(RbacAuthEntity entity) {
        // auth id -> auth entity
        redisTemplate.opsForHash().delete(
                RbacAuthConstants.AUTH_ID_KEY, entity.getId());
        // auth code -> auth id
        redisTemplate.opsForHash().delete(
                RbacAuthConstants.AUTH_CODE_ID_KEY
                        .concat(String.valueOf(entity.getTenantId())),
                entity.getAuthCode());
    }
}
